package edu.fourtwelve.client;

import edu.fourtwelve.srlzd_data.MemberData;
import edu.fourtwelve.srlzd_data.PasswordVerificationData;
import edu.fourtwelve.util.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.*;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;

/**
 *
 * @author Christopher C. Hunt
 */
public class ClientLogin extends javax.swing.JFrame implements BankingConstants {

    /**
     * Creates new form Client
     */
    public ClientLogin() {
        LoggingSetup.setupLogging(true);
        initConnection();
        initComponents();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        userNameJLabel = new javax.swing.JLabel();
        userNameJTextField = new javax.swing.JTextField();
        paswordJLabel = new javax.swing.JLabel();
        createUserJButton = new javax.swing.JButton();
        loginJButton = new javax.swing.JButton();
        passJPasswordField = new javax.swing.JPasswordField();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Banking Login");
        setResizable(false);

        userNameJLabel.setText("User Name:");

        paswordJLabel.setText("Password:");

        createUserJButton.setText("Create User");
        createUserJButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                createUserJButtonActionPerformed(evt);
            }
        });

        loginJButton.setText("Login");
        loginJButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                loginJButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(100, 100, 100)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(paswordJLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(passJPasswordField))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(userNameJLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(userNameJTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 137, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(createUserJButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(loginJButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
                .addContainerGap(103, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(82, 82, 82)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(userNameJLabel)
                    .addComponent(userNameJTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(paswordJLabel)
                    .addComponent(passJPasswordField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(43, 43, 43)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(createUserJButton)
                    .addComponent(loginJButton))
                .addContainerGap(94, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void createUserJButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createUserJButtonActionPerformed
        try {
            String usrName = userNameJTextField.getText();
            if (usrName != null && !usrName.equals("")) {
                final MemberData usrData = new MemberData(usrName);
                this.objOutStream.writeObject(RequestType.CREATE);
                this.objOutStream.flush();
                final ClientLogin cLogIn = this;

                initPUPRKeyExchange();
                this.getKeys.execute();

                SwingWorker<RequestType, Void> getRequestType = new SwingWorker<RequestType, Void>() {

                    private final Object WAIT_NOTIFY = new Object();

                    @Override
                    protected RequestType doInBackground() throws Exception {
                        ClientRegistration.showFrame(cLogIn, WAIT_NOTIFY, usrData);
                        cLogIn.setVisible(false);

                        while (!ClientRegistration.finished) {
                            synchronized (WAIT_NOTIFY) {
                                WAIT_NOTIFY.wait();
                            }
                        }

                        getKeys.get();

                        RequestType ret = (RequestType) objInStream.readObject();
                        return ret;
                    }

                    @Override
                    public void done() {
                        if (ClientRegistration.successful) {
                            ClientRegistration.finished = false;
                            respondToUserNameRequest(this, usrData);
                        } else {
                            ClientRegistration.finished = false;
                            MemberData userData = new MemberData("");
                            respondToUserNameRequest(this, userData);
                        }
                    }
                };
                getRequestType.execute();
            } else {
                JOptionPane.showMessageDialog(this,
                        "You must Supply a User Name",
                        "Missing User Name",
                        JOptionPane.ERROR_MESSAGE);
            }
        } catch (IOException e) {
            log.log(Level.SEVERE, "Failed to write to output stream", e);
        }
    }//GEN-LAST:event_createUserJButtonActionPerformed

    public void respondToUserNameRequest(final SwingWorker fRequest, final MemberData usrData) {
        try {
            if ((RequestType.USER_NAME).equals((fRequest.get()))) {

                if (log.isLoggable(Level.FINE)) {
                    log.log(Level.FINE, "User Name = {0}", usrData.getUsrName());
                }

                Cipher cipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, this.symKeyGen.getSecretKey(),
                        this.symKeyGen.getIVParams());

                SealedObject sealedObj = new SealedObject(usrData, cipher);

                this.objOutStream.writeObject((Object) sealedObj);
                this.objOutStream.flush();

                SwingWorker<Boolean, Void> getResponse = new SwingWorker<Boolean, Void>() {

                    @Override
                    protected Boolean doInBackground() throws Exception {
                        boolean ret = objInStream.readBoolean();
                        return ret;
                    }

                    @Override
                    public void done() {
                        notifyUserNameResult(this);
                    }
                };
                getResponse.execute();
            } else {
                log.log(Level.SEVERE, "Invalid Request");
            }
        } catch (IOException ex) {
            log.log(Level.SEVERE, null, ex);
        } catch (InterruptedException ex) {
            log.log(Level.SEVERE, null, ex);
        } catch (ExecutionException ex) {
            log.log(Level.SEVERE, null, ex);
        } catch (IllegalBlockSizeException e) {
            log.log(Level.SEVERE, "Failed to write to output stream", e);
        } catch (InvalidKeyException e) {
            log.log(Level.SEVERE, "Failed to write to output stream", e);
        } catch (NoSuchAlgorithmException e) {
            log.log(Level.SEVERE, "Failed to write to output stream", e);
        } catch (NoSuchPaddingException e) {
            log.log(Level.SEVERE, "Failed to write to output stream", e);
        } catch (InvalidParameterException e) {
            log.log(Level.SEVERE, "Failed to write to output stream", e);
        } catch (InvalidAlgorithmParameterException e) {
            log.log(Level.SEVERE, "Failed to write to output stream", e);
        }
    }

    public void notifyUserNameResult(final Future<Boolean> fBool) {
        try {
            boolean valid = fBool.get();
            if (valid) {
                JOptionPane.showMessageDialog(this,
                        "Success",
                        "User Name added to the DB",
                        JOptionPane.INFORMATION_MESSAGE);
            } else {
                JOptionPane.showMessageDialog(this,
                        "Invalid User Name",
                        "User Name already exhists",
                        JOptionPane.ERROR_MESSAGE);
            }
        } catch (InterruptedException e) {
            log.log(Level.SEVERE, "Failure in notifyUserNameResult");
        } catch (ExecutionException e) {
            log.log(Level.SEVERE, "Failure in notifyUserNameResult");
        }
    }

    private void loginJButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_loginJButtonActionPerformed
        try {
            String usrName = userNameJTextField.getText();
            char[] passField = passJPasswordField.getPassword();

            if (usrName != null && !usrName.equals("") && passField != null
                    && (!Arrays.equals(passField, new char[]{}))
                    && passField.length > MIN_PASS_LENGTH) {
                PasswordVerificationData usrData = new PasswordVerificationData(usrName);
                this.objOutStream.writeObject(RequestType.AUTHENTICATE);
                this.objOutStream.flush();
                this.objOutStream.writeObject(usrData);
                this.objOutStream.flush();
                usrData = (PasswordVerificationData) this.objInStream.readObject();
                usrData.setPassword(hashPass(usrData));
                passJPasswordField.setText("");
                userNameJTextField.setText("");
                final PasswordVerificationData loginData = usrData.copy(usrData);
                final ClientLogin cLogIn = this;

                initPUPRKeyExchange();
                this.getKeys.execute();

                SwingWorker<RequestType, Void> getRequestType = new SwingWorker<RequestType, Void>() {

                    @Override
                    protected RequestType doInBackground() throws Exception {
                        getKeys.get();

                        RequestType ret = (RequestType) objInStream.readObject();
                        return ret;
                    }

                    @Override
                    public void done() {
                        respondToUserNameRequestLogin(cLogIn, this, loginData);
                    }
                };
                getRequestType.execute();
            } else {
                JOptionPane.showMessageDialog(this,
                        "You must Supply a User Name",
                        "Missing User Name",
                        JOptionPane.ERROR_MESSAGE);
            }
        } catch (IOException e) {
            log.log(Level.SEVERE, "Failed to write to output stream", e);
        } catch (ClassNotFoundException e) {
            log.log(Level.SEVERE, "Failed to write to output stream", e);
        }
    }//GEN-LAST:event_loginJButtonActionPerformed

    public void respondToUserNameRequestLogin(final ClientLogin cLogIn, final SwingWorker fRequest,
            final PasswordVerificationData usrData) {
        try {
            if ((RequestType.USER_NAME).equals((fRequest.get()))) {

                if (log.isLoggable(Level.FINE)) {
                    log.log(Level.FINE, "User Name = {0}", usrData.getUsrName());
                }

                final Cipher cipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, this.symKeyGen.getSecretKey(),
                        this.symKeyGen.getIVParams());
                final Cipher decipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
                decipher.init(Cipher.DECRYPT_MODE, this.symKeyGen.getSecretKey(),
                        this.symKeyGen.getIVParams());

                SealedObject sealedObj = new SealedObject(usrData, cipher);

                this.objOutStream.writeObject((Object) sealedObj);
                this.objOutStream.flush();

                SwingWorker<Boolean, Void> getResponse = new SwingWorker<Boolean, Void>() {

                    @Override
                    protected Boolean doInBackground() throws Exception {
                        boolean ret = objInStream.readBoolean();
                        return ret;
                    }

                    @Override
                    public void done() {
                        notifyUserNameResultLogin(cLogIn, this, cipher, decipher);
                    }
                };
                getResponse.execute();
            } else {
                log.log(Level.SEVERE, "Invalid Request");
            }
        } catch (IOException ex) {
            log.log(Level.SEVERE, null, ex);
        } catch (InterruptedException ex) {
            log.log(Level.SEVERE, null, ex);
        } catch (ExecutionException ex) {
            log.log(Level.SEVERE, null, ex);
        } catch (IllegalBlockSizeException e) {
            log.log(Level.SEVERE, "Crypto Error ", e);
        } catch (InvalidKeyException e) {
            log.log(Level.SEVERE, "Crypto Error ", e);
        } catch (NoSuchAlgorithmException e) {
            log.log(Level.SEVERE, "Crypto Error ", e);
        } catch (NoSuchPaddingException e) {
            log.log(Level.SEVERE, "Crypto Error ", e);
        } catch (InvalidParameterException e) {
            log.log(Level.SEVERE, "Crypto Error ", e);
        } catch (InvalidAlgorithmParameterException e) {
            log.log(Level.SEVERE, "Crypto Error ", e);
        }
    }

    public void notifyUserNameResultLogin(final ClientLogin cLogIn, final Future<Boolean> fBool,
            final Cipher cipher, final Cipher decipher) {
        try {
            boolean valid = fBool.get();
            if (valid) {
                this.objOutStream.writeObject(RequestType.GET_ACCOUNT_DATA);
                this.objOutStream.flush();
                
                cLogIn.setVisible(false);
                ClientAccounts.showFrame(cLogIn, socket, objInStream, objOutStream, cipher, decipher);
            } else {
                JOptionPane.showMessageDialog(this,
                        "Login Failed",
                        "User Name and Password do not match records",
                        JOptionPane.ERROR_MESSAGE);
            }
        } catch (IOException e) {
            log.log(Level.SEVERE, "Failure in notifyUserNameResult");
        } catch (InterruptedException e) {
            log.log(Level.SEVERE, "Failure in notifyUserNameResult");
        } catch (ExecutionException e) {
            log.log(Level.SEVERE, "Failure in notifyUserNameResult");
        }
    }

    private byte[] hashPass(PasswordVerificationData usrData) {
        MessageDigest digest = null;
        byte[] retVal;

        try {
            digest = MessageDigest.getInstance(HASHING_FUNC);
        } catch (NoSuchAlgorithmException ex) {
            log.log(Level.SEVERE, null, ex);
        }
        char[] pass = this.passJPasswordField.getPassword();
        byte[] passB = new byte[pass.length];


        for (int i = 0; i < pass.length; i++) {
            passB[i] = (byte) pass[i];
        }

        digest.reset();
        digest.update(usrData.getSalt());

        try {
            log.log(Level.INFO, "passwordField= {0} pass= {1}",
                    new Object[]{new String(pass), new String(passB, DEFAULT_ENC)});
        } catch (UnsupportedEncodingException ex) {
            log.log(Level.SEVERE, null, ex);
        }

        retVal = digest.digest(passB);
        log.log(Level.INFO,
                "hash= {0}", new Object[]{Utils.toHexString(retVal)
                });

        Arrays.fill(passB, (byte) '0');
        Arrays.fill(pass, '0');

        digest.reset();
        digest.update(usrData.getNonce());
        retVal = digest.digest(retVal);

        try {
            log.log(Level.INFO, "passwordField= {0} pass= {1}",
                    new Object[]{new String(pass), new String(passB, DEFAULT_ENC)});
        } catch (UnsupportedEncodingException ex) {
            log.log(Level.SEVERE, null, ex);
        }

        log.log(Level.INFO,
                "hash= {0}", new Object[]{Utils.toHexString(retVal)
                });

        return retVal;
    }

    private void initConnection() {
        try {
            this.socket = new java.net.Socket(java.net.InetAddress.getByName("localhost").getHostAddress(), PORT);
            this.objOutStream = new java.io.ObjectOutputStream(this.socket.getOutputStream());
            this.objInStream = new java.io.ObjectInputStream(this.socket.getInputStream());
            this.asymKeyGen = new AsymetricKeyGen();
        } catch (java.io.IOException e) {
            log.log(Level.SEVERE, "Failed to setup Server Connection", e);
        } catch (BankingException e) {
            log.log(Level.SEVERE, "Failed to setup Server Connection", e);
        }
    }

    private void initPUPRKeyExchange() {
        getKeys = new SwingWorker<Void, Void>() {

            @Override
            protected Void doInBackground() throws Exception {
                try {
                    puKey = (PublicKey) objInStream.readObject();


                    objOutStream.writeObject((Object) asymKeyGen.getPublicKey());
                    objOutStream.flush();

                    SealedObject sealedSecretKey = (SealedObject) objInStream.readObject();
                    SealedObject sealedIV = (SealedObject) objInStream.readObject();

                    Cipher decipher = Cipher.getInstance(ASYMETRIC_ALGORITHM);
                    decipher.init(Cipher.DECRYPT_MODE, asymKeyGen.getPrivateKey());

                    SecretKey secretKey = (SecretKey) sealedSecretKey.getObject(decipher);
                    byte[] iv = (byte[]) sealedIV.getObject(decipher);

                    symKeyGen = new SymetricKeyGen(secretKey, iv);

                    log.log(Level.INFO, "MyPublicKey = {0} YourPublicKey= {1} SecretKey= {2} IV= {3}",
                            new Object[]{Utils.toHexString(asymKeyGen.getPublicKey().getEncoded()),
                                Utils.toHexString(puKey.getEncoded()),
                                Utils.toHexString(symKeyGen.getSecretKey().getEncoded()),
                                Utils.toHexString(symKeyGen.getIV())});
                } catch (ClassNotFoundException e) {
                    log.log(Level.SEVERE, "Failed to write to output stream", e);
                } catch (IllegalBlockSizeException e) {
                    log.log(Level.SEVERE, "Failed to write to output stream", e);
                } catch (InvalidKeyException e) {
                    log.log(Level.SEVERE, "Failed to write to output stream", e);
                } catch (NoSuchAlgorithmException e) {
                    log.log(Level.SEVERE, "Failed to write to output stream", e);
                } catch (NoSuchPaddingException e) {
                    log.log(Level.SEVERE, "Failed to write to output stream", e);
                } catch (BadPaddingException e) {
                    log.log(Level.SEVERE, "Failed to write to output stream", e);
                } catch (BankingException e) {
                    log.log(Level.SEVERE, "Failed to write to output stream", e);
                }
                return null;
            }

            @Override
            public void done() {
            }
        };
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /*
         * Set the Nimbus look and feel
         */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /*
         * If Nimbus (introduced in Java SE 6) is not available, stay with the
         * default look and feel. For details see
         * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;






                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(ClientLogin.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(ClientLogin.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(ClientLogin.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(ClientLogin.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /*
         * Create and display the form
         */
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new ClientLogin().setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton createUserJButton;
    private javax.swing.JButton loginJButton;
    private javax.swing.JPasswordField passJPasswordField;
    private javax.swing.JLabel paswordJLabel;
    private javax.swing.JLabel userNameJLabel;
    private javax.swing.JTextField userNameJTextField;
    // End of variables declaration//GEN-END:variables
    //Non-Design variables declaration
    private static final Logger log = Logger.getLogger(ClientLogin.class.getName());
    private java.net.Socket socket;
    private java.io.ObjectInputStream objInStream;
    private java.io.ObjectOutputStream objOutStream;
    private PublicKey puKey;
    private AsymetricKeyGen asymKeyGen;
    private SymetricKeyGen symKeyGen;
    private SwingWorker<Void, Void> getKeys;
    //End Non-Design variables declaration
}
